iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0

在 TypeScript 中,我們可以使用 type 關鍵字來創建自定義的類型別名。它可以用來定義各種複雜的類型,使我們能夠更好地描述程式碼中的資料結構和變數類型,從而提高程式碼的可讀性和可維護性。威爾豬習慣在類型別名加個前綴字 T,這樣一看就可以知道是使用 type 這個方式來定義,當然看團隊或個人的習慣。(雖然官方不推薦加上前綴就是~ 因為如果使用了前綴,當 type 要改成 interface 時,那所有依賴的地方都要更改,可能會造成不必要的負擔。)

物件類型別名

要特別注意的是:每個屬性的結尾是加入「 ; 」,而不是「 , 」。

假設我們需要每個員工都有一組 ID 編號和名字:

type TEmployee = {
  id: number;
  name: string;
};

const employee1: TEmployee = { id: 1, name: "威爾豬" };
const employee2: TEmployee = { id: 2, name: "威爾羊" };

再看另一個例子:

假設我們訂位需要知道顧客名稱和預定人數:

type TOrder = {
  name: string;
  num: number;
};

const greet = (order: TOrder): string => {
  return `您好, ${order.name}! 您的訂位人數是 ${order.num} 位嗎?`;
};

const customer: TOrder = { name: "威爾豬", num: 3 };
console.log(greet(customer)); // 輸出: 您好, 威爾豬! 您的訂位人數是 3 位嗎?

我們也可以結合已經有的類型,創建更複雜的類型。例如:

type TPoint = {
  x: number;
  y: number;
};

type TCircle = {
  center: TPoint;
  radius: number;
};

這邊我們使用現有的 TPoint 類型來定義一個 TCircle 類型,它包含一個 center 屬性(是一個 TPoint 類型的物件)和一個 radius 屬性。

聯合類型別名

假設我們要做一個隨機數字,大於 50 就顯示贏 100 元,小於或等於 50 就顯示 null:

type TWin = string | null;

const num = Math.round(Math.random() * 100);
const maybeWin: TWin = num > 50 ? "贏了 100 元" : null;

console.log(num);
console.log(maybeWin);

我們也可以使用之前介紹的 枚舉值,來創建新的聯合類型別名:

enum Phone {
  Apple,
  Samsung,
  Mi,
}

enum Color {
  Gold,
  Yellow,
  White,
}

type TPhoneOrColor = Phone | Color;

const selectPhone: TPhoneOrColor = Phone.Mi;
const selectColor: TPhoneOrColor = Color.Yellow;

console.log(selectPhone); // 輸出: 2
console.log(selectColor); // 輸出: 1

擴充類型別名

類型別名可以使用 交叉類型 (&) 的方式讓自定義的類型別名得以擴充。看以下範例:

假設我們已經定義了一個 TPerson 的類型別名,並在其它地方已使用過,但現在有一個地方需要多增加一個地址來使用,我們並不希望重複造輪子,就可以使用擴充的方式來撰寫,看以下範例:

type TPerson = {
  name: string;
  age: number;
};

type TExtendPerson = TPerson & {
  address: string;
  email: string;
}; // 擴充 address 和 email 屬性

再來看另一個例子:

原本老闆只要用到 2D,所以我們創建了 TPoint 的類型別名,並已在多個地方快樂使用,結果現在突然有一個要使用 3D:

type TPoint = {
  x: number;
  y: number;
};

type TPoint3D = TPoint & { z: number }; // 擴充 z 屬性

不過威爾豬通常會使用 可選串連 (?) 的方式來擴充,當然不一樣的情況時也會使用交叉類型的方式,以上述的範例來說:

type TPerson = {
  name: string;
  age: number;
  address?: string; // 可選
  email?: string; // 可選
};
type TPoint = {
  x: number;
  y: number;
  z?: number; // 可選
};

這樣不用再額外定義一個新的類型別名,也可以在各個地方通用,程式碼精簡又方便。

泛型類型別名

我們還可以創建 泛型類型別名,這可以將一個或多個型別參數傳遞給類型別名,以創建在不同型別上使用的泛型結構。

type TGeneric<T> = {
  value: T;
};

type TGenerics<T, U> = {
  first: T;
  second: U;
};

遞迴類型別名

假設我們需要描述一個具有 嵌套結構的數據,就可以使用遞迴型別別名。


type TNode<T> = {
  value: T;
  children?: TNode<T>[];
};

const rootNode: TNode<number> = {
  value: 1,
  children: [
    {
      value: 2,
      children: [
        {
          value: 3,
          children: []
        },
      ]
    },
    {
      value: 4,
      children: []
    }
  ]
};

除非我們的結構是像這樣嵌套式的,不然威爾豬非常不建議在其它地方也使用這樣的方式,因為它們會存在一些限制和潛在的問題:

  • 複雜性: 當型別具有深層次的嵌套結構時,這可能會使程式碼變得難以理解和維護。

  • 性能問題: 遞迴類型別名可能導致型別檢查過程變得更加複雜,從而增加 TypeScript 編譯的時間,在某些情況下,過多的遞迴可能會導致性能下降。

  • 不適用於所有情況: 遞迴型別別名主要用於描述嵌套結構的數據,如樹狀結構或深度巢狀的物件。對於扁平結構或單純的型別,就別這樣使用了。 例如:

// 不適合使用遞迴類型別名

type TArray<T> = T[];
const numberArr: TArray<number> = [1, 2, 3, 4, 5];

在這種情況下,直接使用 number[] 就足夠了,沒必要增加不必要的複雜性。

選擇是否使用遞迴類型別名應該基於 專案的數據結構和實際需求。如果我們的數據具有嵌套結構,而且使用遞迴類型別名能夠使程式碼更清晰和更容易理解,則可以考慮使用它們,但應盡量簡化型別的結構。所以在使用遞迴類型別名之前,應先評估程式碼的需求並考慮 是否有更簡單的方式來表示型別


總之,類型別名的主要優勢是增強了程式碼的可讀性和可維護性,使我們能夠為複雜的資料結構創建自訂的類型別名,也可以根據類型別名來檢查變數的類型是否符合預期。不過創建類型別名時,還是要使用有意義的命名,確保每個別名都有實際的價值,也不要濫用而導致程式碼變得更複雜和難以理解。


上一篇
斷言(Assertion)
下一篇
接口 / 介面 (Interface)
系列文
用不到 30 天學會基本 TypeScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言